package pl.mmadej.ds4java.ds.array;


import pl.mmadej.ds4java.ds.common.DataStructure;
import pl.mmadej.ds4java.ds.stack.LinkedListStack;
import pl.mmadej.ds4java.ds.stack.Stack;

import java.util.Arrays;
import java.util.Comparator;

import static java.lang.System.arraycopy;

//intended to be used internally by other classes
public class ArrayDecorator<T> extends DataStructure<T> {

    private Object[] array;

    public ArrayDecorator(int length) {
        assertLength(length);
        this.array = new Object[length];
    }

    public void setAt(int index, T element) {
        while (index > length() - 1) {
            doubleArray();
        }
        array[index] = element;
    }

    @SuppressWarnings("unchecked")
    public T elementAt(int index) {
        return (T) array[index];
    }

    public Object[] getArray() {
        return array;
    }

    public void setArray(Object[] array) {
        this.array = array;
    }

    public int length() {
        return array.length;
    }

    private void assertLength(int lengthToAssert) {
        if (lengthToAssert < 1) {
            throw new IllegalArgumentException("Size cannot be less than one");
        }
    }

    @SuppressWarnings("unchecked")
    public <E> E[] toArray(E[] resultedArray, int from, int to) {
        resultedArray = createArrayIfNeeded(resultedArray, to - from);
        arraycopy(array, from, resultedArray, 0, to - from);
        return resultedArray;
    }

    protected void doubleArray() {
        Object[] newArray = new Object[array.length * 2];
        arraycopy(array, 0, newArray, 0, array.length);
        array = newArray;
    }

    public void quickSort(int startIndex, int endIndex, Comparator<T> comparator) {
        Stack<Integer> stack = new LinkedListStack<Integer>();
        stack.push(startIndex);
        stack.push(endIndex);
        while (!stack.empty()) {
            endIndex = stack.pop();
            startIndex = stack.pop();
            if (startIndex < endIndex) {
                int partitionIndex = partition(startIndex, endIndex, comparator);
                stack.push(startIndex);
                stack.push(partitionIndex - 1);
                stack.push(partitionIndex + 1);
                stack.push(endIndex);
            }
        }

    }

    protected int partition(int startIndex, int endIndex, Comparator<T> comparator) {
        int partitionIndex = startIndex;
        T partitionElement = elementAt(endIndex);
        for (int index = startIndex; index < endIndex; index++) {
            if (comparator.compare(elementAt(index), partitionElement) < 0) {
                swapElementsAt(partitionIndex++, index);
            }
        }
        swapElementsAt(partitionIndex, endIndex);
        return partitionIndex;
    }

    public void swapElementsAt(int firstElementIndex, int secondElementIndex) {
        T temporarySwapVariable = elementAt(firstElementIndex);
        setAt(firstElementIndex, elementAt(secondElementIndex));
        setAt(secondElementIndex, temporarySwapVariable);
    }

    public void clear() {
        for (int index = 0; index < array.length; index++) {
            setAt(index, null);  //for gc
        }
    }

    public void reverse(int startIndex, int size) {
        int countIndex = 0;
        for (int index = startIndex; index < startIndex + size / 2; index++) {
            swapElementsAt(index, (startIndex + size - 1) - countIndex++);
        }
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(array);
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean equals(Object obj) {
        if (!(obj instanceof ArrayDecorator)) {
            return false;
        }
        ArrayDecorator<T> other = (ArrayDecorator<T>) obj;
        return Arrays.equals(array, other.array);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("");
        int iMax = array.length - 1;
        sb.append('[');
        for (int i = 0; ; i++) {
            sb.append(array[i] == this ? "{This ArrayDecorator}" : String.valueOf(array[i]));
            if (i == iMax) {
                return sb.append(']').toString();
            }
            sb.append(", ");
        }
    }
}
